package wechatMiniprogram

import (
	"bytes"
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"encoding/xml"
	"errors"
	"fmt"
	"io"
	"math"
	"net/http"
	"sort"
	"strconv"
	"strings"
	"time"

	"goadmin/logs"
	"goadmin/utils"
)

// PayClient 微信支付 JSAPI 下单客户端
type PayClient struct {
	AppID     string
	MchID     string
	MchKey    string // 32 位 APIv2 密钥
	NotifyURL string
}

// PayRequest 统一下单请求
type PayRequest struct {
	AppID          string `xml:"appid"`
	Body           string `xml:"body"`
	MchID          string `xml:"mch_id"`
	NonceStr       string `xml:"nonce_str"`
	NotifyURL      string `xml:"notify_url"`
	OpenID         string `xml:"openid"`
	OutTradeNo     string `xml:"out_trade_no"`
	SpbillCreateIP string `xml:"spbill_create_ip"`
	TotalFee       int    `xml:"total_fee"`
	Attach         string `xml:"attach,omitempty"`
	TradeType      string `xml:"trade_type"`
	Sign           string `xml:"sign"`
}

// PayResponse 统一下单返回
type PayResponse struct {
	ReturnCode string `xml:"return_code"`
	ResultCode string `xml:"result_code"`
	PrepayID   string `xml:"prepay_id"`
	ReturnMsg  string `xml:"return_msg"`
}

// JsapiPayParams 前端调起 JSAPI 需要的 5 个参数
type JsapiPayParams struct {
	AppID     string `json:"appId"`
	NonceStr  string `json:"nonceStr"`
	Package   string `json:"package"`
	SignType  string `json:"signType"`
	TimeStamp string `json:"timeStamp"`
	PaySign   string `json:"paySign"`
}

// GetPayParameter 获取小程序调起支付参数
func (c *PayClient) GetPayParameter(outTradeNo, openID string, price float64, desc, attach string) (*JsapiPayParams, error) {
	if attach == "" {
		attach = "order"
	}
	totalFee := int(math.Round(price * 100))

	// 1. 统一下单
	nonce := utils.RandomString(32)
	req := PayRequest{
		AppID:          c.AppID,
		Body:           desc,
		MchID:          c.MchID,
		NonceStr:       nonce,
		NotifyURL:      c.NotifyURL,
		OpenID:         openID,
		OutTradeNo:     outTradeNo,
		SpbillCreateIP: c.realClientIP(),
		TotalFee:       totalFee,
		Attach:         attach,
		TradeType:      "JSAPI",
	}
	req.Sign = c.makeSign(map[string]string{
		"appid":            req.AppID,
		"body":             req.Body,
		"mch_id":           req.MchID,
		"nonce_str":        req.NonceStr,
		"notify_url":       req.NotifyURL,
		"openid":           req.OpenID,
		"out_trade_no":     req.OutTradeNo,
		"spbill_create_ip": req.SpbillCreateIP,
		"total_fee":        strconv.Itoa(req.TotalFee),
		"trade_type":       req.TradeType,
		"attach":           req.Attach, // 空值会在makeSign被过滤
	})

	raw, err := c.postXML("https://api.mch.weixin.qq.com/pay/unifiedorder", req)
	if err != nil {
		return nil, err
	}
	var resp PayResponse
	if err := xml.Unmarshal(raw, &resp); err != nil {
		return nil, err
	}
	if resp.ReturnCode != "SUCCESS" || resp.ResultCode != "SUCCESS" {
		return nil, errors.New("wx unifiedorder fail: " + resp.ReturnMsg)
	}

	// 2. 二次签名
	jsapiNonce := utils.RandomString(32)
	timeStamp := fmt.Sprintf("%d", time.Now().Unix())
	params := map[string]string{
		"appId":     c.AppID,
		"nonceStr":  jsapiNonce,
		"package":   "prepay_id=" + resp.PrepayID,
		"signType":  "MD5",
		"timeStamp": timeStamp,
	}
	paySign := c.makeSign(params)

	return &JsapiPayParams{
		AppID:     c.AppID,
		NonceStr:  jsapiNonce,
		Package:   params["package"],
		SignType:  "MD5",
		TimeStamp: timeStamp,
		PaySign:   paySign,
	}, nil
}

// ============ 工具函数 ============

// 生成微信 MD5 签名（过滤空值，key 转小写）
func (c *PayClient) makeSign(m map[string]string) string {
	keys := make([]string, 0, len(m))
	for k, v := range m {
		if v == "" {
			continue
		}
		keys = append(keys, strings.ToLower(k)) // 强制小写
	}
	sort.Strings(keys)

	var buf bytes.Buffer
	for i, k := range keys {
		if i > 0 {
			buf.WriteString("&")
		}
		buf.WriteString(k)
		buf.WriteString("=")
		buf.WriteString(m[k])
	}
	buf.WriteString("&key=" + c.MchKey)

	sum := md5.Sum(buf.Bytes())
	signStr := strings.ToUpper(hex.EncodeToString(sum[:]))
	logs.Sugar().Infow("sign plain", "plain", buf.String(), "sign", signStr)
	return signStr
}

// post XML 并返回原始字节
func (c *PayClient) postXML(url string, v interface{}) ([]byte, error) {
	raw, _ := xml.Marshal(v)
	logs.Sugar().Infow("post xml", "xml", string(raw))
	resp, err := http.Post(url, "application/xml; charset=utf-8", bytes.NewReader(raw))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	return io.ReadAll(resp.Body)
}

// 结构体转 map（key 统一小写）
func structToMap(v interface{}) map[string]string {
	b, _ := json.Marshal(v)
	var m map[string]interface{}
	_ = json.Unmarshal(b, &m)
	out := make(map[string]string, len(m))
	for k, v := range m {
		out[strings.ToLower(k)] = fmt.Sprint(v) // 强制小写
	}
	return out
}

// 获取客户端 IP（生产环境请改真实逻辑）
func (c *PayClient) realClientIP() string {
	return "123.123.123.123"
}
